/**
 * jsMotif cross-browser dom enabler for document manipulation and events used by <code>{@link jsMotif.Template}</code>.
 * 
 * @namespace
 * @publish
 */
jsMotif.dom = (function () {
    /* Local reference to document, because accessing a local variable is faster then a global one */
    var _D = document;

    function byId (id) {
        return _D.getElementById(id);
    }

    /**
     * Always returns a DOM element - if an id is passed as a parameter it lookups an element
     * with that id otherwise it returns the element passed as the parameter. This is a helper function
     * for writing functions that accept both element ids and element objects.
     *
     * @private
     * @param {Object} el a DOM element or a element's id
     * @param {Object} a DOM element or null if the requested element wasn't found
     */
    function _elem (el) {
        return typeof el === 'string' ? byId(el) : el;
    }

    function byTagName (tagName,/*?*/ startElem) {
        return (startElem || _D).getElementsByTagName(tagName);
    }

    function extendNodeAttributes (node, attributes) {
        var attr;
        for (attr in attributes) {
            if (attributes.hasOwnProperty(attr)) {
                if (attr === 'class') {
                    node.className = attributes[attr];
                }else{
                    node[attr] = attributes[attr];
                }
            }
        }
        return node;
    }
    
    function remove (elem) {
        var node = _elem(elem);
        if(node && node.parentNode){
            node.parentNode.removeChild(node);
        }
    }

    function create(tag, /*?*/params, /*?*/body) {
        var node = _D.createElement(tag);
        node = extendNodeAttributes(node, params);
        if (body && typeof body === 'object' && body.appendChild) {
            node.appendChild(body);
        } else if (typeof body === 'string') {
            node.appendChild(_D.createTextNode(body));
        }
        return node;
    }

    function createFragment () {
        return _D.createDocumentFragment();
    }

    function append (elem, childNode) {
        var parentNode = _elem(elem);
        if (parentNode) {
            parentNode.appendChild(childNode);    
        }
    }
    
    /**
     * Moves childs from one node to another
     * @param {Object} target node
     * @param {Object} src node
     */
    function moveChildNodes (target, src) {
        var node = src.firstChild;
        while(node){
            append(target, node);
            node = src.firstChild;
        }
    }

    /**
     * Function stores a event handler for the event so all handlers can be removed later on
     *
     * @private
     * @param {Object} node a dom element which have an event added
     * @param {String} event event name
     * @param {Function} handler event handler
     */
    function _saveHandler (node, event, handler) {
        var path = '_' + event + 'Handlers';
        if (node) {
            node[path] = node[path] || [];
            node[path].push(handler);
        }

        return true;
    }


    function getMouseRelatedTarget (event) {
        event = event || window.event;
        var relTarget = event.relatedTarget;
        if (!relTarget) {
            if (event.type === "mouseout") {
                relTarget = event.toElement;
            } else if (event.type === "mouseover") {
                relTarget = event.fromElement;
            }
        }
        return relTarget;
    }

    function isContainedWithin (parent, child) {
        while (child && child !== parent) {
            child = child.parentNode;
        }
        return child === parent;
    }

    function _emulateMouseEnterLeave (element, handler) {
        return function (e) {
                var target = getMouseRelatedTarget(e);
                if (!isContainedWithin(element, target)) {
                    handler(e);
                }
        };
    }

    function addEvent (elem, event, handler, capture) {
        var node = _elem(elem),
            fn;

        if (!node) {
            return;
        }
        if (node.addEventListener) {
            fn = function (elem, event, handler, capture) {
                var node = _elem(elem);
                if (node) {
                    var isCaptured = typeof capture !== 'undefined' ? capture : false;
                    if (event === 'mouseenter' || event === 'mouseleave') {
                        var nativeEvent = event === 'mouseenter' ? 'mouseover' : 'mouseout';

                        handler = _emulateMouseEnterLeave(node, handler);
                        node.addEventListener(nativeEvent, handler, isCaptured);
                        _saveHandler(node, event, handler);

                    } else {

                        _saveHandler(node, event, handler);
                        node.addEventListener(event, handler, isCaptured);
                        //FF registers the mouse wheel event in a different way than the rest
                        if (event === 'mousewheel') {
                            node.addEventListener("DOMMouseScroll", handler, isCaptured);
                        }

                    }
                }
                return handler;
            };
        } else if (node.attachEvent) {
            fn = function (elem, event, handler) {
                var node = _elem(elem);
                if(node){
                    var newHandler = function() {
                        handler.call(elem, window.event, arguments);
                    };
                    _saveHandler(node,event,newHandler);
                    node.attachEvent('on' + event, newHandler);
                    return newHandler;
                }
            };
        }

        if (fn) {
            jsMotif.dom.addEvent = fn;
            return fn(node, event, handler, capture);
        }
    }

    function hide () {
        var node,
            dispStyle,
            alen = arguments.length;

        while (alen--) {
            node = _elem(arguments[alen]);
            if (node) {
                dispStyle = node.style.display;
                node._lastDisplay = (dispStyle !== "none") ? dispStyle : "";
                node.style.display = "none";
            }
        }
    }


    function show () {
        var node,
            alen = arguments.length;

        while (alen--) {
            node = _elem(arguments[alen]);
            if (node) {
                node.style.display = node._lastDisplay || "";
            }
        }
    }

    function stopPropagation (e) {
        var event = e || window.event;
        event.cancelBubble = true;
        if(event.stopPropagation){
            event.stopPropagation();
        }
    }

    function getEventSource (e) {
        var event = e || window.event;
        return event.target || event.srcElement;
    }

    function preventDefault (e) {
        var event = e || window.event;
        event.returnValue = false;
        if (event.preventDefault) {
            event.preventDefault();
        }
    }

    /** @lends jsMotif.dom*/
    return {
        /**
         * Retrieves the DOM element with the given id.
         *
         * @function
         * @publish
         * 
         * @param {String} id id of the requested element
         * @return {Object} a DOM node or null if the requested element wasn't found
         */
        byId: byId,

        /**
         * Creates a DOM element object based on input parameters.
         *
         * @function
         * @publish
         *
         * @param tag {String} tag name of the element
         * @param params {Object} a javascript object with properties which describe element attributes; optional
         * @param body {String|Object} a body of the element (text or DOM element); optional
         * @return {Object} created element
         */
        create: create,

        /**
         * Creates a document fragment.
         *
         * @function
         * @publish
         *
         * @return {Object} created DOM fragment
         */
        createFragment: createFragment,

        /**
         * Appends given node(s) to a parent. Can be easly combined with create function. This is a
         * variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem parent node object or its id
         * @param {Object} childNode-s an element/elements to be added in the same order as provided to arguments
         */
        append: append,

        /**
         * Registers a new event listener. Returned handler may be different than the passed one (event emulation).
         * Remember to use the returned handler for event removal. Supported event names are dom events without 'on' prefix
         * i.e <code>mouseup</code>, <code>click</code>. Plus <code>mouseenter</code> and <code>mouseleave</code> events emulation.  
         * 
         * @function
         * @publish
         *
         * @param {String|Object} elem a DOM element or its id
         * @param {String} event lower-case event name without 'on' prefix 
         * @param {Function} handler handler function
         * @param {Boolean} capture default: false
         * @return event handler, may be different than the passed one if event emulation is needed
         */
        addEvent: addEvent,

        /**
         * Hides an element. This is a variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem-s one or more DOM elements or their ids
         */
        hide: hide,

        /**
         * Shows the hidden element. This is a variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem-s one or more DOM elements or their ids
         */
        show: show,

        /**
         * Stops propagation of an event, abstract layer for handling stopping propagation in IE
         * and other browsers because they handle it differently.
         *
         * @function
         * @publish
         *
         * @param {Object} e an event object
         */
        stopPropagation: stopPropagation,

        /**
         * Extends the given node with the given attributes.
         *
         * @function
         * @publish
         *
         * @param {Object} node a DOM node
         * @param {Object} attributes a javascript object containing attributes map
         * @return {Object} extended object
         */
        extendNodeAttributes: extendNodeAttributes,

        /**
         * Checks if the child parameter is a child of the parent parameter in the DOM or if the
         * child parameter is equal to the parent parameter.
         *
         * @function
         * @publish
         *
         * @param {Object} parent a DOM object
         * @param {Object} child a DOM object
         * @return {Boolean} true if the child parameter is a child of the parent parameter
         */
        isContainedWithin: isContainedWithin,

        /**
         * Cross browser method for retrieving relatedTarget property of a mouse event
         * as returned by FF.
         *
         * @function
         * @publish
         *
         * @param {Object} event a mouse event to retrieve the relatedTarget from
         * @return {Object} relatedTarget property of the event parameter
         */
        getMouseRelatedTarget: getMouseRelatedTarget,

        /**
         * Cross browser method for retrieving event source element.
         *
         * @function
         * @publish
         *
         * @param {Object} event an event object to retrieve source element from
         * @return {Object} source element property of the event
         */
        getEventSource: getEventSource,

        /**
         * Prevents default action of an event, abstract layer for preventing default actions in IE
         * and other browsers because they handle it differently.
         *
         * @function
         * @publish
         *
         * @param {Object} e an event object
         */
        preventDefault: preventDefault,
        
        /**
         * Function checks and returns element. If dom element is provided then 
         * it returns the element if string provided it tries to fetch element by id.
         * @function
         * @publish
         * 
         * @param {String|Object} element id of dome element or element reference
         */
        elem: _elem, 
        
        /**
         * Removes Node from DOM
         * @function
         * @publish
         * 
         * @param {Object} dom node
         */
        remove: remove,
        
        moveChildNodes: moveChildNodes
    };
}());
